import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Stack;

 /**
  * @author Zach built on a skeleton built by John Mayer
  */
public class Project2Panel extends JPanel implements KeyListener, MyGL {

   double rotX, rotY, rotZ;
   boolean perspective = false;
   Graphics canvasGraphics;
   
   //My stuff
   ArrayList<Points> lineList = new ArrayList();
   Stack<double[]> matrixStack = new Stack();
   double[] currentMatrix;
   int width;
   int height;

   private class Points{
	   double x;
	   double y;
	   Points(double x, double y, double z){
		   double[] newPoint = squish(x, y, z);
		   this.x = (width / 2) + ((width / 2) * newPoint[0]);
		   this.y = (height / 2) - ((height / 2) * newPoint[1]);
	   }
   }
   
   private double[] squish(double x, double y, double z){
	   double[] newPoint = new double[]{x, y, z, 1};
	   double[] result = new double[4];
	   for(int i = 0; i < 4; i++){
		   result[0] += currentMatrix[i] * newPoint[i]; 
		   result[1] += currentMatrix[i + 4] * newPoint[i]; 
		   result[2] += currentMatrix[i + 8] * newPoint[i];
		   result[3] += currentMatrix[i + 12] * newPoint[i];
	   }
	   if(perspective){
		   result[0] *= 1/result[3];
		   result[1] *= 1/result[3];
		   result[2] *= 1/result[3];
	   }
	   return result;
   }
   
   private double[] matrixMult4x4(double[] one, double[] two){
	   double[] result = new double[16];
	   for(int i = 0; i < 4; i++)
		   for(int j = 0; j < 4; j++)
			   for(int counter = 0; counter < 4; counter++)
				   result[i*4 + j] += one[i * 4 + counter] * two [j + counter * 4];
	   return result;
   }
   
   @Override
   public void glBegin() {
	   // TODO My Method
	   lineList = new ArrayList();
   }

   @Override
   public void glEnd() {
	   // TODO My Method
	   // Draw the stuff
	   int i = lineList.size()-1;
	   while(i > 0){
		   canvasGraphics.drawLine((int)lineList.get(i).x, (int)lineList.get(i).y, 
				   (int)lineList.get(i-1).x, (int)lineList.get(i-1).y);
		   i--;
	   }
	   canvasGraphics.drawLine((int)lineList.get(0).x, (int)lineList.get(0).y,
			   (int)lineList.get(lineList.size()-1).x, (int)lineList.get(lineList.size()-1).y);
   }

   @Override
   public void glFrustum(double l, double r, double b, double t, 
                       double n, double f) {
	   // TODO My Method
	   double[] frustumMatrix = new double[]{
			   (n)/(r), 0, 0, 0,
			    0, (n)/(t), 0, 0,
			    0, 0, -(f+n)/(f-n), -(2*f*n)/(f-n),
			    0, 0, -1, 0};
	   currentMatrix = matrixMult4x4(currentMatrix, frustumMatrix);
   }

   @Override
   public void glLoadIdentity() {
	   // TODO My Method
      currentMatrix = new double[]{
    		  1,0,0,0,
    		  0,1,0,0,
    		  0,0,1,0,
    		  0,0,0,1};
   }

   @Override
   public void glOrtho(double l, double r, double b, double t, 
                     double n, double f) {
	   // TODO My Method
	   double[] orthoMatrix = new double[]{
			   2/(r-l), 0, 0, -(r+l)/(r-l),
			   0, 2/(t-b), 0, -(t+b)/(t-b),
			   0, 0, 2/(n-f), -(f+n)/(f-n),
			   0, 0, 0, 1};
	   currentMatrix = matrixMult4x4(currentMatrix, orthoMatrix);
   }

   @Override
   public void glPopMatrix() {
	   // TODO My Method
      currentMatrix = matrixStack.pop();
   }

   @Override
   public void glPushMatrix() {
	   // TODO My Method
      matrixStack.push(currentMatrix);
   }

   @Override
   public void glRotate(double angle, double x, double y, double z) {
	   // TODO My Method
	  double[] rotate = null;
      if(x == 1 && y ==0 && z == 0){
    	  rotate = new double[]{
    		  1,0,0,0,
	    	  0,Math.cos(Math.toRadians(angle)),-Math.sin(Math.toRadians(angle)),0,
	    	  0,Math.sin(Math.toRadians(angle)),Math.cos(Math.toRadians(angle)),0,
	    	  0,0,0,1};
      }
      else if(x == 0 && y ==1 && z == 0){
    	  rotate = new double[]{
    		  Math.cos(Math.toRadians(angle)),0,Math.sin(Math.toRadians(angle)),0,
        	  0,1,0,0,
        	  -Math.sin(Math.toRadians(angle)),0,Math.cos(Math.toRadians(angle)),0,
        	  0,0,0,1};
      }
      else if(x == 0 && y ==0 && z == 1){
    	  rotate = new double[]{
    		  Math.cos(Math.toRadians(angle)),-Math.sin(Math.toRadians(angle)),0,0,
    		  Math.sin(Math.toRadians(angle)),Math.cos(Math.toRadians(angle)),0,0,
        	  0,0,1,0,
        	  0,0,0,1};
      }
      else
    	  System.out.println("Durr...");
	  currentMatrix = matrixMult4x4(currentMatrix, rotate);
   }

   @Override
   public void glScale(double sx, double sy, double sz) {
	   // TODO My Method
	   double[] scaleMatrix = new double[]{
			   sx,0,0,0,
			   0,sy,0,0,
			   0,0,sz,0,
			   0,0,0,1};
	   currentMatrix = matrixMult4x4(currentMatrix, scaleMatrix);
   }

   @Override
   public void glTranslate(double dx, double dy, double dz) {
	   // TODO My Method
	   double[] transMatrix = new double[]{
			   1,0,0,dx,
			   0,1,0,dy,
			   0,0,1,dz,
			   0,0,0,1};
	   currentMatrix = matrixMult4x4(currentMatrix, transMatrix);
   }

   @Override
   public void glVertex(double x, double y, double z) {
	   // TODO My Method
      lineList.add(new Points(x, y, z));
   }

   @Override
   public void glViewport(int x, int y) {
	   // TODO My Method
      width = x;
      height = y;
   }  
   
   
   void doCube( 
         double x1, double x2, 
         double y1, double y2, 
         double z1, double z2) {

      // FRONT face
      glBegin();   
      glVertex(x1, y1, z1);
      glVertex(x2, y1, z1); 
      glVertex(x2, y2, z1); 
      glVertex(x1, y2, z1);   
      glEnd();              

      // BACK face
      glBegin();   
      glVertex(x1, y1, z2);
      glVertex(x2, y1, z2); 
      glVertex(x2, y2, z2); 
      glVertex(x1, y2, z2);   
      glEnd();  

      // LEFT face      
      glBegin();   
      glVertex(x1, y1, z1);
      glVertex(x1, y2, z1); 
      glVertex(x1, y2, z2); 
      glVertex(x1, y1, z2);   
      glEnd();       

      // RIGHT face
      glBegin();   
      glVertex(x2, y1, z1);
      glVertex(x2, y2, z1); 
      glVertex(x2, y2, z2); 
      glVertex(x2, y1, z2);   
      glEnd();        

      // BOTTOM
      glBegin();   
      glVertex(x1, y1, z1);
      glVertex(x2, y1, z1); 
      glVertex(x2, y1, z2); 
      glVertex(x1, y1, z2);   
      glEnd();        

      // TOP   
      glBegin();   
      glVertex(x1, y2, z1);
      glVertex(x2, y2, z1); 
      glVertex(x2, y2, z2); 
      glVertex(x1, y2, z2);   
      glEnd();                                   

   }                          


   void cube() {
      doCube(-0.5, 0.5, -0.5, 0.5, 0.5, -0.5);     
   }     

   protected void paintComponent(Graphics g) {
      super.paintComponent(g); 
      canvasGraphics = g;
      setBackground(Color.white);

      Dimension dim = getSize();   
      glViewport(dim.width,dim.height);

      // apply projection matrix
      glLoadIdentity(); 
      if (perspective)
          glFrustum(-0.75,0.75,-0.75,0.75,1,4.5);
      else
         glOrtho(-1.5, 1.5, -1.5, 1.5, -10.0, 10.0);
      
      // move object away from camera
      glTranslate(0,-.25,-2.5);

      // do interactive rotations
      glRotate(rotX,1,0,0);
      glRotate(rotY,0,1,0);
      glRotate(rotZ,0,0,1);
   
      // create and transform objects
        // first the tower    
      glPushMatrix();
      glTranslate(0.75,0.25,-0.75);
      glScale(0.5,1.5,0.5);      
      cube();     
      glPopMatrix();

        // then the main building      
      cube(); 
      
   }

   @Override
   public void keyPressed(KeyEvent key) {
      switch (key.getKeyCode()) {
          case KeyEvent.VK_P:
              perspective = true;
              repaint();
              break;
          case KeyEvent.VK_N:
              perspective = false;
              repaint();
              break;            
          case KeyEvent.VK_ESCAPE:
              System.exit(0);
              break;
          case KeyEvent.VK_LEFT:
              rotY -= 3;
              repaint();
              break;  
          case KeyEvent.VK_RIGHT:
              rotY += 3;
              repaint();
              break;  
          case KeyEvent.VK_UP:
              rotX -= 3;
              repaint();            
              break;  
          case KeyEvent.VK_DOWN:
              rotX += 3;
              repaint();
              break;  
          case KeyEvent.VK_PAGE_UP:
              rotZ += 3;
              repaint();
              break;
          case KeyEvent.VK_PAGE_DOWN:
              rotZ -= 3;
              repaint();
              break;                                                                                             
          default:
              //System.out.println(key.getKeyCode());
              break;
      }
   }

   @Override
   public void keyReleased(KeyEvent e) {
      // TODO Auto-generated method stub

   }

   @Override
   public void keyTyped(KeyEvent e) {
      // TODO Auto-generated method stub

   }
   
}

